package util;

import exception.SoiRuntimeException;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

/**
 * @className: ClassLoaderUtil
 * @author: ocxz1
 * @date: 2025/3/11 下午2:48
 * @Version: 1.0
 * @description: tmp-study
 */
public class ClassLoaderUtil {

    public static List<Class<?>> loadClassesInPackage(String packageName, List<String> excludedPackages) {
        if (packageName == null) {
            return Collections.emptyList();
        }

        List<Class<?>> classes = new ArrayList<>();
        if (packageName.endsWith(".class")) {
            findClasses(packageName, classes, excludedPackages);
            return classes;
        }

        String path = packageName.replace('.', '/');
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                File directory = new File(resource.getFile());
                if (directory.exists()) {
                    findClasses(directory, packageName, classes, excludedPackages);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new SoiRuntimeException(e);
        }
        return classes;
    }

    private static void findClasses(File directory, String packageName, List<Class<?>> classes, List<String> excludedPackages)
            throws ClassNotFoundException {
        if (!directory.exists()) {
            return;
        }
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    String subPackageName = packageName + "." + file.getName();
                    if (!isExcluded(subPackageName, excludedPackages)) {
                        findClasses(file, subPackageName, classes, excludedPackages);
                    }
                } else if (file.getName().endsWith(".class")) {
                    String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                    if (!isExcluded(className, excludedPackages)) {
                        classes.add(Class.forName(className));
                    }
                }
            }
        }
    }

    private static void findClasses(String className, List<Class<?>> classes, List<String> excludedPackages) {

        if (className == null || !className.endsWith(".class")) {
            return;
        }

        className = className.replace(".class", "");

        String packageName = className.substring(0, className.lastIndexOf('.'));
        if (isExcluded(packageName, excludedPackages)) {
            return;
        }
        try {
            classes.add(Class.forName(className));
        } catch (Exception e) {
            throw new SoiRuntimeException(e);
        }

    }

    private static boolean isExcluded(String name, List<String> excludedPackages) {
        for (String excludedPackage : excludedPackages) {
            if (name.startsWith(excludedPackage)) {
                return true;
            }
        }
        return false;
    }
}
